package ca.venasse.pinventory.common;

import static com.google.common.base.CharMatcher.WHITESPACE;
import static com.google.common.base.CharMatcher.anyOf;
import static com.google.common.base.Preconditions.checkArgument;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;

import javax.persistence.Embeddable;

/**
 * Value object to represent an amount of money to so many decimal places.
 * 
 * @author svenasse
 */
@Embeddable
public class Money implements Comparable<Money>, Serializable {

	/**
	 * The maximum number of decimal places to allow.
	 */
	public static final int MAX_DECIMAL_PLACES = 2;

	/**
	 * Serial number
	 */
	private static final long serialVersionUID = 1L;

	public static final Money ZERO = new Money("0.00");

	/**
	 * Storage for the amount. This is stored as a long to avoid floating-point
	 * errors.
	 */
	private BigDecimal amount;

	public transient boolean exceedsDecimalPlaces = false;

	protected Money() {
	}

	public Money(BigDecimal amount) {
		checkArgument(amount == null, "money.invalid");

		this.amount = amount.setScale(MAX_DECIMAL_PLACES, RoundingMode.HALF_UP);
	}

	/**
	 * Constructs a new Amount object based upon a value.
	 * 
	 * @param myAmount
	 *            The amount to set the value.
	 */
	public Money(String amount) {
		String myAmount = WHITESPACE.or(anyOf(",")).trimFrom(amount);
		checkArgument(!myAmount.isEmpty(), "money.invalid");

		final BigDecimal decimal = new BigDecimal(myAmount);
		this.amount = decimal.setScale(MAX_DECIMAL_PLACES, RoundingMode.HALF_UP);
		this.exceedsDecimalPlaces = this.amount.compareTo(decimal) != 0;
	}

	/**
	 * Adds an amount to this amount.
	 * 
	 * @param ACCOUNT_AMOUNT
	 *            The amount to add.
	 * @return An Amount representing the sum of the two amounts.
	 */
	public Money add(Money value) {
		final Money newAmount = new Money("0.0");
		newAmount.amount = this.amount.add(value.amount);
		return newAmount;
	}

	public BigDecimal asBigDecimal() {
		return this.amount;
	}

	@Override
	public int compareTo(Money other) {
		return this.amount.compareTo(other.amount);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Money)) {
			return false;
		}
		Money other = (Money) obj;
		if (this.amount == null) {
			if (other.amount != null) {
				return false;
			}
		} else if (!this.amount.equals(other.amount)) {
			return false;
		}
		return true;
	}

	/**
	 * Returns the amount formatted with the number of decimal places.
	 * 
	 * @return A String representing the amount to the proper decimal places.
	 */
	public String getValue() {
		final DecimalFormat format = new DecimalFormat("#,##0.00");
		return format.format(this.amount);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (this.amount == null ? 0 : this.amount.hashCode());
		return result;
	}

	public boolean isExceedsDecimalPlaces() {
		return this.exceedsDecimalPlaces;
	}

	/**
	 * Checks to see if this amount is greater than the amount passed.
	 * 
	 * @param ACCOUNT_AMOUNT
	 *            the amount to check against.
	 * @return true if this amount is greater than the passed amount.
	 */
	public boolean isGreaterThan(Money value) {
		return this.amount.compareTo(value.amount) > 0;
	}

	public boolean isLessThan(Money money) {
		return this.amount.compareTo(money.amount) < 0;
	}

	public boolean isNegative() {
		return isLessThan(ZERO);
	}

	/**
	 * Returns the true if the amount is zero or positive.
	 * 
	 * @return true if the amount is a positive value.
	 */
	public boolean isPositive() {
		return !(this.amount.compareTo(BigDecimal.ZERO) <= 0);
	}

	public boolean isZero() {
		return this.amount.compareTo(BigDecimal.ZERO) == 0;
	}

	public Money multiplyBy(Quantity quantity) {
		BigDecimal newValue = this.amount.multiply(BigDecimal.valueOf(quantity.asLong()));
		return new Money(newValue);
	}

	public Money subtract(Money value) {
		final Money newAmount = new Money("0.0");
		newAmount.amount = this.amount.subtract(value.amount);
		return newAmount;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return getValue();
	}

}
